package fr.inserm.um915.bdibd.model;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.imageio.ImageIO;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;

import com.sleepycat.je.Cursor;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.Transaction;

public class ImageBuilder
	{
	@SuppressWarnings("unused")
	private static final Logger LOG=Logger.getLogger("fr.inserm.um915.bdibd");
	
	static public class PairFam
		{
		private String f1;
		private String f2;
		public PairFam(String f1,String f2)
			{
			if(f1.compareTo(f2)<0)
				{
				this.f1=f1;
				this.f2=f2;
				}
			else
				{
				this.f1=f2;
				this.f2=f1;
				}
			}
		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + f1.hashCode();
			result = prime * result + f2.hashCode();
			return result;
		}
		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			PairFam other = (PairFam) obj;
			return f1.equals(other.f1) && f2.equals(other.f2);
			}
		@Override
		public String toString() {
			if(f1.equals(f2)) return f1;
			return f1+" "+f2;
			}
		}
	
	
	static public class ChromInfo
		{
		byte chrom;
		int chromStart=0;
		int chromEnd=0;
		Rectangle bounds=new Rectangle();
		int size()
			{
			return 1+(chromEnd-chromStart);
			}
		double convertPosToPixel(int pos)
			{
			return this.bounds.x+((pos-this.chromStart)/(double)this.size())*((double)this.bounds.width);
			}
		}
	static public class FamData
		{
		PairFam family;
		public BufferedImage image;
		private Graphics2D g;
		double minY=0;
		double maxY=0;
		Set<Integer> pairids=new HashSet<Integer>();
		DataExtractor dataExtractor;
		}
	

	
	
	
	double minY=0;
	double maxY=0;
	//float limit=0;
	Insets margin=new Insets(50, 100, 100, 50);
	Dimension frameDim=new Dimension(1000,300);
	public Map<Byte, ChromInfo> id2chrom=new TreeMap<Byte,ChromInfo>();
	public Map<PairFam,FamData> fam2famData=new HashMap<PairFam,FamData>();
	/** regions to be hightlighted */
	public List<ChromStartEnd> regionsHighLight=Collections.emptyList();
	private long genome_size=0;
	JProgressBar progress=null;
	private DataExtractorFactory dataExtractorFactory=null;
	
	public void build(
			DataExtractorFactory dataExtractorFactory,
			ChromPosition regionBegin,
			ChromPosition regionEnd,
			Set<Integer> pairids
		
			)
		{

		ChromInfo prev_chromosome=null;
		this.dataExtractorFactory=(DataExtractorFactory)dataExtractorFactory.clone();
		Model model=dataExtractorFactory.getModel();
		Transaction txn=dataExtractorFactory.getTransaction();
		//prepar data per families
		for(Integer pairid:pairids)
			{
			
			PairOfIndividual indis=model.getPairOfIndividualByPairId(txn, pairid);
			PairFam pfam=new PairFam(indis.get(0).getFamily(), indis.get(1).getFamily());
			
			FamData famData=this.fam2famData.get(pfam);
			if(famData!=null)
				{
				famData.pairids.add(pairid);
				continue;
				}
			famData=new FamData();
			famData.dataExtractor=this.dataExtractorFactory.createDataExtractor();
			famData.family=pfam;
			
			
			famData.image=new BufferedImage(
					frameDim.width+margin.left+margin.right,
					frameDim.height+margin.top+margin.bottom,
					BufferedImage.TYPE_INT_ARGB);
			famData.g=famData.image.createGraphics();
			famData.g.setFont(new Font("Courier",Font.PLAIN,10));
			
			famData.pairids.add(pairid);
			this.fam2famData.put(pfam, famData);
			}
		
		/** initialize dataExtractor with the pair of indi IDS */
		for(FamData famData:this.fam2famData.values())
			{
			famData.dataExtractor.setPairOfIndividualIds(new ArrayList<Integer>(famData.pairids));
			}
		
		//two pass algorithm
		for(int side=0;side<2;++side)
			{
			int count_markers=0;
			Cursor c=null;
			try
				{
				DatabaseEntry key=new DatabaseEntry();
				DatabaseEntry value=new DatabaseEntry();
				boolean first=true;
				c=model.getPosToMarkerDatabase().openCursor(txn, null);
				if(regionBegin!=null)
					{
					ChromPosition.BINDING.objectToEntry(regionBegin, key);
					}
				for(;;)
					{
					OperationStatus ret;
					if(first && regionBegin!=null)
						{
						ret=c.getSearchKeyRange(key, value, LockMode.DEFAULT);
						}
					else
						{
						ret=c.getNext(key, value, LockMode.DEFAULT);
						}
					first=false;
					if(ret!=OperationStatus.SUCCESS) break;
					ChromPosition pos=ChromPosition.BINDING.entryToObject(key);
					Marker marker=Marker.BINDING.entryToObject(value);
					LinkageRow lkRow= model.getIBDByMarker(txn, marker.getName());
					if(lkRow==null || lkRow.isEmpty()) continue;
					
					if(regionEnd!=null)
						{
						if(pos.getChromosomeId()>regionEnd.getChromosomeId()) break;
						if(pos.getChromosomeId()==regionEnd.getChromosomeId())
							{
							if(pos.getPosition()> regionEnd.getPosition()) break;
							}
						}
					++count_markers;
					if(progress!=null && count_markers%10000==0)
						{
						final String msg="Step "+(side+1)+"/2 :"+count_markers +" markers.";
						try {
							SwingUtilities.invokeAndWait(new Runnable()
								{
								@Override
								public void run() {
									progress.setString(msg);
									}
								});
							} 
						catch (Exception e) {
							}
						}
					
					
					
					ChromInfo ci=prev_chromosome;
					if(ci==null || ci.chrom!=pos.getChromosomeId())
						{
						ci=	this.id2chrom.get(pos.getChromosomeId());
						}
					
					//first pass
					if(side==0)
						{
						//first pass, get max pair in each family having ibd0< limit
						for(FamData famData:this.fam2famData.values())
							{
							Double v=famData.dataExtractor.getValue(lkRow);
							if(v==null) continue;
							famData.minY=Math.min(famData.minY,v);
							famData.maxY=Math.max(famData.maxY,v);
							
							}
						//adjust chromStart/chromEnd
						if(ci==null)
							{
							ci=new ChromInfo();
							ci.chrom=pos.getChromosomeId();
							ci.chromStart=pos.getPosition();
							ci.chromEnd=pos.getPosition();
							this.id2chrom.put(ci.chrom,ci);
							}
						ci.chromStart=Math.min(pos.getPosition(), ci.chromStart);
						ci.chromEnd=Math.max(pos.getPosition(), ci.chromEnd);
						}
					else //2nd pass, we plot the data
						{
						double x= ci.convertPosToPixel(pos.getPosition());
						
						for(FamData famData:this.fam2famData.values())
							{
							Double v=famData.dataExtractor.getValue(lkRow);
							if(v==null) continue;
							
							double y= this.margin.top+this.frameDim.height- (v/(this.maxY-this.minY))*this.frameDim.height;
							
							famData.g.setColor(Color.BLUE);
							famData.g.drawLine((int)x-3,(int)y, (int)x+3,(int)y);
							famData.g.drawLine((int)x,(int)y-3, (int)x,(int)y+3);
							}
						}
					prev_chromosome=ci;
					}
				}
			finally
				{
				if(c!=null) c.close();
				}
			
			if(side==0)
				{
				this.genome_size=0;
				for(ChromInfo ci:this.id2chrom.values())
					{
					ci.chromEnd=Math.max(0, ci.chromEnd-10);
					ci.chromStart+=10;
					this.genome_size+=ci.size();
					}
				int x=margin.left;
				for(ChromInfo ci:this.id2chrom.values())
					{
					ci.bounds.x=x;
					ci.bounds.y=margin.top;
					ci.bounds.height=this.frameDim.height;
					ci.bounds.width=(int)((this.frameDim.width/(double)this.genome_size)*ci.size());
					
					for(FamData famData:this.fam2famData.values())
						{
						Graphics2D g=famData.g;
						//chr background
						g.setColor(ci.chrom%2==1?Color.WHITE:Color.LIGHT_GRAY);
						g.fill(ci.bounds);
						//chr frame
						g.setColor(Color.GRAY);
						g.draw(ci.bounds);
						
						//chr title
						String chrom=ChromPosition.denormalize(ci.chrom);
						Rectangle2D dim=g.getFontMetrics().getStringBounds(chrom, g);
						g.setColor(Color.GRAY);
						g.drawString(chrom,
								ci.bounds.x+ci.bounds.width/2-(int)dim.getWidth()/2,
								this.margin.top-10
								);
						//selected regions
						if(this.regionsHighLight!=null)
							{
							Composite oldc=g.getComposite();
							
							for(ChromStartEnd cse:this.regionsHighLight)
								{
								if(cse.getChromosomeId()!=ci.chrom) continue;
								if(cse.getStart() > ci.chromEnd) continue;
								if(cse.getEnd() < ci.chromStart) continue;
								int rStart=Math.min(cse.getStart(),ci.chromStart);
								int rEnd=Math.min(cse.getEnd(),ci.chromEnd);
								Rectangle2D.Double rectHighlight=new Rectangle2D.Double(
									ci.convertPosToPixel(rStart),
									ci.bounds.y,
									ci.convertPosToPixel(rEnd)-ci.convertPosToPixel(rStart),
									ci.bounds.height
									);
								g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.4f));
								g.setColor(Color.PINK);
								g.fill(rectHighlight);
								g.setColor(Color.RED);
								g.draw(rectHighlight);
								}
							g.setComposite(oldc);
							}
						}
					x+=ci.bounds.width;
					}
				
				
				for(FamData famData:this.fam2famData.values())
					{
					this.minY=Math.min(this.minY,famData.minY);
					this.maxY=Math.max(this.maxY,famData.maxY);
					}
				this.maxY+=1;
				//LOG.info("maxY:"+maxY);
				for(FamData famData:this.fam2famData.values())
					{
					Graphics2D g=famData.g;
					final int STEP=10;
					g.setColor(Color.GRAY);
					//draw Y axis
					for(int i=0;i<=STEP;++i)
						{
						double y=margin.top+this.frameDim.height-((double)this.frameDim.height/(double)STEP)*i;
						g.drawLine(margin.left,(int)y,margin.left+this.frameDim.width,(int)y);
						double y_value=this.minY+((this.maxY-this.minY)/STEP)*i;
						g.drawString(y_value==(double)((int)Math.floor(y_value))?
								String.valueOf((int)y_value): String.valueOf(y_value), 10,(int)y);
						}
					//draw family label
					g.drawString(famData.family.toString(), 0, 12);
					}
				
				}
			}
		
		for(FamData famData:this.fam2famData.values())
			{
			if(famData.g!=null) famData.g.dispose();
			}
		}
	
	public DataExtractorFactory getDataExtractorFactory()
		{
		return dataExtractorFactory;
		}
	
	public void saveAs(File fileout) throws IOException
		{
		if(this.fam2famData.size()==1)
			{
			ImageIO.write(this.fam2famData.values().iterator().next().image, "PNG", fileout);
			}
		else
			{
			ZipOutputStream zout=new ZipOutputStream(new FileOutputStream(fileout));
			int i=0;
			for(FamData fam:this.fam2famData.values())
				{
				ZipEntry ze=new ZipEntry("manhattan"+(i+1)+"png");
				zout.putNextEntry(ze);
				ImageIO.write(fam.image, "PNG",zout);
				zout.closeEntry();
				++i;
				}
			zout.finish();
			zout.flush();
			zout.close();
			}
		}
	}
